package com.ksyun.seckill.keygen;

import com.google.common.base.Preconditions;
import lombok.SneakyThrows;

import java.util.Calendar;

/**
 * Snowflake key generate algorithm.
 *
 * <pre>
 *     Length of key is 64 bit.
 *     1 bit sign bit.
 *     41 bits timestamp offset from 2017.01.01 to now.
 *     10 bits worker process id.
 *     12 bits auto increment offset in one mills
 * </pre>
 */
public final class SnowflakeKeyGenerator {

    public static final long EPOCH;

    private static final long SEQUENCE_BITS = 12L;

    private static final long WORKER_ID_BITS = 10L;

    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

    private static final int DEFAULT_VIBRATION_VALUE = 1;

    private static final int MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS = 10;

    private final int maxVibrationOffset = DEFAULT_VIBRATION_VALUE;

    private final int maxTolerateTimeDifferenceMilliseconds = MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS;

    /**
     * workId
     */
    private static final int DEFAULT_WORKER_ID = 0;

    private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;

    private static long workerId = DEFAULT_WORKER_ID;

    private volatile int sequenceOffset = -1;

    private volatile long sequence;

    private volatile long lastMilliseconds;


    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2017, Calendar.JANUARY, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    public synchronized Long generateKey() {
        long currentMilliseconds = getCurrentMillis();
        if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {
            currentMilliseconds = getCurrentMillis();
        }
        if (lastMilliseconds == currentMilliseconds) {
            if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {
                currentMilliseconds = waitUntilNextTime(currentMilliseconds);
            }
        } else {
            vibrateSequenceOffset();
            sequence = sequenceOffset;
        }
        lastMilliseconds = currentMilliseconds;
        return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }

    @SneakyThrows(InterruptedException.class)
    private boolean waitTolerateTimeDifferenceIfNeed(final long currentMilliseconds) {
        if (lastMilliseconds <= currentMilliseconds) {
            return false;
        }
        long timeDifferenceMilliseconds = lastMilliseconds - currentMilliseconds;
        if (!(timeDifferenceMilliseconds < maxTolerateTimeDifferenceMilliseconds)) {
            throw new IllegalArgumentException("Clock is moving backwards, last time is " + lastMilliseconds + " milliseconds, current time is " + currentMilliseconds + " milliseconds.");
        }
        Thread.sleep(timeDifferenceMilliseconds);
        return true;
    }

    private long waitUntilNextTime(final long lastTime) {
        long result = getCurrentMillis();
        while (result <= lastTime) {
            result = getCurrentMillis();
        }
        return result;
    }

    private void vibrateSequenceOffset() {
        sequenceOffset = sequenceOffset >= maxVibrationOffset ? 0 : sequenceOffset + 1;
    }

    private long getWorkerId() {
        return workerId;
    }

    /**
     * 设置WorkId，该方法可定制.
     */
    public static void setWorkerId(final long workerId) {
        Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
        SnowflakeKeyGenerator.workerId = workerId;
    }

    public long getCurrentMillis() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        System.out.println(SEQUENCE_MASK);
        System.out.println(WORKER_ID_LEFT_SHIFT_BITS);


        System.out.println(System.currentTimeMillis());
        SnowflakeKeyGenerator key = new SnowflakeKeyGenerator();
        System.out.println((System.currentTimeMillis() - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS);
        System.out.println(key.generateKey());
        System.out.println(key.generateKey());
    }


}
